﻿using System;
using System.Windows.Media;
using System.Collections.Generic;
using System.Windows.Input;
using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using T0yK4T.IO;
using System.Windows.Documents;

namespace ToyChat.BuiltinModules
{
    /// <summary>
    /// Interaction logic for ChannelPane.xaml
    /// </summary>
    public partial class ChannelPane : UserControl, IPane
    {
        private IUserInterface ui;
        private RemoteChannel channel;
        private Core module;
        private bool wasKicked = false;
        private Paragraph toplevelParagraph = new Paragraph();
        private Dictionary<int, Color> userColors = new Dictionary<int, Color>();

        /// <summary>
        /// Initializes a new (empty) instance of <see cref="ChannelPane"/>
        /// </summary>
        public ChannelPane()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Initializes a new instance of <see cref="ChannelPane"/>
        /// </summary>
        /// <param name="channel">The channel that this <see cref="ChannelPane"/> should contain</param>
        /// <param name="module">The <see cref="Core"/> that owns the pane</param>
        public ChannelPane(RemoteChannel channel, Core module)
            : this()
        {
            this.module = module;
            this.channel = channel;
            foreach (IUser user in channel.Users)
            {
                if (user.UserID == this.module.CurrentUser.UserID)
                    this.userColors.Add(user.UserID, Colors.Crimson);
                else
                    this.userColors.Add(user.UserID, this.RandomColor());
                this.nickList1.Items.Add(user);
            }
            this.channel.UserUpdated += new UserUpdatedHandler(channel_UserUpdated);
            this.channel.Reloaded += new Action(channel_Reloaded);
            this.channel.MessageReceived += new Action<BasicMessage, IUser>(channel_MessageReceived);
            this.channel.UserJoined += new UserEventHandler(channel_UserJoined);
            this.channel.UserLeft += new RemoteChannel.UserLeftEventHandler(channel_UserLeft);
            this.messageBox1.Document.Blocks.Add(this.toplevelParagraph);
            this.WriteLine(new Run(string.Format("Joined [{0}]: {1}", this.channel.ChannelName, this.channel.MotD)) { Foreground = new SolidColorBrush(Colors.DarkCyan) });
        }

        /// <summary>
        /// Handler for <see cref="RemoteChannel.UserLeft"/>
        /// </summary>
        /// <param name="sender">The sender of the actual message (the "kicker" if you will)</param>
        /// <param name="user">The user that left</param>
        /// <param name="reason">The reason the user left</param>
        void channel_UserLeft(IUser sender, IUser user, string reason)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<IUser, IUser, string>((s, u, r) => this.channel_UserLeft(s, u, r)), sender, user, reason);
            else
            {
                if (this.nickList1.Items.Contains(user))
                {
                    if (wasKicked = (this.module.CurrentUser != null && user.UserID == this.module.CurrentUser.UserID && sender == null))
                    {
                        System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(o => MessageBox.Show(string.Format("You were kicked from {0} by an unknown user", this.channel.ChannelName), "Kicked", MessageBoxButton.OK, MessageBoxImage.Hand)));
                        this.nickList1.Items.Clear();
                    }
                    else if (wasKicked = (this.module.CurrentUser != null && user.UserID == this.module.CurrentUser.UserID && sender.UserID != user.UserID))
                    {
                        System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(o => MessageBox.Show(string.Format("You were kicked from {0} by {1}", this.channel.ChannelName, o), "Kicked", MessageBoxButton.OK, MessageBoxImage.Hand)), sender);
                        this.nickList1.Items.Clear();
                    }
                    else
                    {
                        this.nickList1.Items.Remove(user);
                        this.userColors.Remove(user.UserID);
                        Run r = new Run() { FontStyle = FontStyles.Italic, FontWeight = FontWeights.Bold };

                        if (sender != null && sender.UserID != user.UserID)
                        {
                            if (!string.IsNullOrEmpty(reason))
                                r.Text = string.Format("[{0}]: Was Kicked by [{1}], \"{2}\"", user, sender, reason);
                            else
                                r.Text = string.Format("[{0}]: Was Kicked by [{1}]", user, sender);
                        }
                        else
                        {
                            if (!string.IsNullOrEmpty(reason))
                                r.Text = string.Format("[{0}]: Left \"{1}\"", user, reason);
                            else
                                r.Text = string.Format("[{0}]: Left...", user);
                        }
                        this.WriteLine(r);
                    }
                }
            }
        }

        Random rnd = new Random();
        private Color RandomColor()
        {
            byte[] values = new byte[3];
            rnd.NextBytes(values);
            Color ret = new Color();
            ret.R = (byte)(values[0] > 200 ? 200 : values[0]);
            ret.G = (byte)(values[1] > 200 ? 200 : values[1]);
            ret.B = (byte)(values[2] > 200 ? 200 : values[2]);
            ret.A = 0xff;
            return ret;
        }

        /// <summary>
        /// Handler for <see cref="RemoteChannel.UserJoined"/>
        /// </summary>
        /// <param name="user">The user that joined the channel</param>
        void channel_UserJoined(IUser user)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<IUser>((u) => this.channel_UserJoined(u)), user);
            else
            {
                if (!this.nickList1.Items.Contains(user))
                {
                    if (!this.userColors.ContainsKey(user.UserID))
                        this.userColors.Add(user.UserID, this.RandomColor());

                    this.nickList1.Items.Add(user);
                    this.WriteLine(new Run(string.Format("[{0}]: Joined the channel", user)) { FontStyle = FontStyles.Italic, FontWeight = FontWeights.Bold });
                }
            }
        }

        /// <summary>
        /// Handler for <see cref="RemoteChannel.MessageReceived"/>
        /// </summary>
        /// <param name="message">The <see cref="BasicMessage"/> that was received by the channel</param>
        /// <param name="sender">The <see cref="IUser"/> that sent the message</param>
        void channel_MessageReceived(BasicMessage message, IUser sender)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<BasicMessage, IUser>((m, u) => this.channel_MessageReceived(m, u)), message, sender);
            else
            {
                Run userRun = new Run();
                if (sender == null)
                {
                    userRun.Text = string.Format("[{0}][SERVER]: ", DateTime.Now.ToShortTimeString());
                    userRun.Foreground = new SolidColorBrush(Colors.Red);
                }
                else
                {
                    userRun.Text = string.Format("[{0}][{1}]: ", DateTime.Now.ToShortTimeString(), sender);
                    Color c;
                    if (this.userColors.TryGetValue(sender.UserID, out c))
                        userRun.Foreground = new SolidColorBrush(c);
                    else
                        userRun.Foreground = new SolidColorBrush(Colors.DarkCyan);
                }

                this.toplevelParagraph.Inlines.Add(userRun);
                this.toplevelParagraph.Inlines.AddRange(this.Parse(message.UTF8Message));
                this.toplevelParagraph.Inlines.Add(new Run(Environment.NewLine));
                this.messageBox1.ScrollToEnd();
            }
        }

        /// <summary>
        /// Handler for <see cref="RemoteChannel.Reloaded"/>
        /// </summary>
        void channel_Reloaded()
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action(() => this.channel_Reloaded()), null);
            else
            {
                this.OnTitleChanged();
                this.nickList1.Items.Clear();
                foreach (IUser user in this.channel.Users)
                    this.nickList1.Items.Add(user);
            }
        }

        /// <summary>
        /// Handler for <see cref="RemoteChannel.UserUpdated"/>
        /// </summary>
        /// <param name="oldUser">The old <see cref="IUser"/> that was updated</param>
        /// <param name="newUser">The new <see cref="IUser"/> object that replaces the old one</param>
        void channel_UserUpdated(IUser oldUser, IUser newUser)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<IUser, IUser>((o, n) => this.channel_UserUpdated(o, n)), oldUser, newUser);
            else
            {
                if (this.nickList1.Items.Contains(oldUser))
                {
                    int index = this.nickList1.Items.IndexOf(oldUser);
                    this.nickList1.Items.RemoveAt(index);
                    if (newUser != null)
                        this.nickList1.Items.Insert(index, newUser);
                }
            }
        }

        /// <summary>
        /// Calls <see cref="Send"/>
        /// </summary>
        /// <param name="sender">should be the button</param>
        /// <param name="e">The arguments of this event</param>
        private void sendButton1_Click(object sender, RoutedEventArgs e)
        {
            this.Send();
        }

        /// <summary>
        /// Gets the current text of the input box, sends it, and clears the input box
        /// </summary>
        private void Send()
        {
            if (this.channel != null && !string.IsNullOrEmpty(this.inputBox1.Text))
            {
                this.channel.Send(this.CreatePacket(this.inputBox1.Text));
                this.inputBox1.Text = string.Empty;
            }
        }

        /// <summary>
        /// "Redefined" to provide easy access to base.CheckAccess() (hidden by intellisense)
        /// </summary>
        /// <returns>base.CheckAccess()</returns>
        protected new bool CheckAccess()
        {
            return base.CheckAccess();
        }

        /// <summary>
        /// Creates a packet from the specified TextRange (this will create an RTFMessage inside the packet)
        /// </summary>
        /// <param name="msg">The message to create a packet for</param>
        /// <returns>A packet containing the specified text (as richtext)</returns>
        private Packet CreatePacket(string msg)
        {
            BasicMessage message = new BasicMessage { UTF8Message = msg };
            Packet pkt = new Packet(this.module.GetID(Core.TYPE_MESSAGE));
            pkt.SerializeData(message);
            return pkt;
        }

        /// <summary>
        /// Adds the specified <see cref="Run"/> to the message box
        /// </summary>
        /// <param name="textRun">The <see cref="Run"/> to add</param>
        private void WriteLine(Run textRun)
        {
            textRun.Text += Environment.NewLine;
            this.toplevelParagraph.Inlines.Add(textRun);
            this.messageBox1.ScrollToEnd();
        }

        /// <summary>
        /// Currently simply returns a new instance of <see cref="Run"/> with it's text set to the specified message
        /// </summary>
        /// <param name="message">The message to "parse"</param>
        /// <returns>a new instance of <see cref="Run"/> with it's text set to the specified message</returns>
        private IEnumerable<Run> Parse(string message)
        {
            return new Run[] { new Run(message) };
        }

        /// <summary>
        /// Called from the UserInterface when this pane is first added
        /// </summary>
        /// <param name="ui">The owner UI</param>
        public void Initialize(IUserInterface ui)
        {
            this.ui = ui;
        }

        /// <summary>
        /// Gets or Sets the Title of this pane
        /// <para/>
        /// Please note that it is only possible to set this value if the user has the permissions to change the channel name
        /// </summary>
        public string Title
        {
            get
            {
                if (this.channel != null)
                    return this.channel.ChannelName;
                else
                    return string.Empty;
            }
            set
            {
                this.channel.ChannelName = value;
                this.OnTitleChanged();
            }
        }

        /// <summary>
        /// Safety net for possible null reference exceptions
        /// </summary>
        private void OnTitleChanged()
        {
            if (this.TitleChanged != null)
                this.TitleChanged.Invoke(this.Title);
        }

        /// <summary>
        /// This event is fired whenever the title of this pane has changed
        /// </summary>
        public event Action<string> TitleChanged;

        /// <summary>
        /// Closes this pane
        /// </summary>
        public void Close()
        {
            if (this.module != null && this.module.CurrentUser != null && !this.wasKicked)
            {
                LeavingMessage ccm = new LeavingMessage() { LeavingReason = "Leaving", ChannelID = this.channel.ChannelID, UserID = this.module.CurrentUser.UserID };
                uint id = module.GetID(Core.TYPE_USERLEFT);
                if (id != 0)
                {
                    Packet pkt = new Packet(id);
                    pkt.ChannelID = this.channel.ChannelID;
                    pkt.SourceUserID = this.module.CurrentUser.UserID;
                    pkt.DestinationUserID = -1;
                    pkt.ChannelID = -1;
                    pkt.SerializeData(ccm);
                    try { this.module.Send(pkt); }
                    catch { }
                }
            }
        }

        /// <summary>
        /// Handler for the KeyDown event of the input box
        /// <para/>
        /// This method takes care of either sending the current message or adding a <see cref="Environment.NewLine"/> to the input box
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> of this event</param>
        private void inputBox1_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                e.Handled = true;
                if (Keyboard.Modifiers == ModifierKeys.Control)
                    this.inputBox1.Text += Environment.NewLine;
                else
                    this.Send();
            }
        }

        /// <summary>
        /// Currently returns true
        /// </summary>
        public bool Closeable
        {
            get { return true; }
        }


        #region Not yet implemented
        private void ulistChat1_Click(object sender, RoutedEventArgs e)
        {
            IUser user;
            if ((user = this.nickList1.SelectedItem as IUser) != null)
            {

            }
        }

        private void ulistKick1_Click(object sender, RoutedEventArgs e)
        {

        }

        private void ulistBan1_Click(object sender, RoutedEventArgs e)
        {

        }
        #endregion Not yet implemented
    }
}
